<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Tutorial Demo</title>

  <style>
    /* 提示框的样式 tooltip */
    .g6-tooltip {
      border: 1px solid #e2e2e2;
      border-radius: 4px;
      font-size: 12px;
      color: #545454;
      background-color: rgba(255, 255, 255, 0.9);
      padding: 10px 8px;
      box-shadow: rgb(174, 174, 174) 0px 0px 10px;
    }
  </style>

</head>
<body>
<!-- 图的画布容器 -->
<div id="mountNode"></div>

<!-- 引入 G6 -->
<script src="https://gw.alipayobjects.com/os/lib/antv/g6/4.7.0/dist/g6.min.js"></script>

<script>

  // 缩略图 (Minimap) 是一种常见的用于快速预览和探索图的工具，可作为导航辅助用户探索大规模图。
  // 实例化 minimap 插件
  const minimap = new G6.Minimap({
    size: [100, 100],
    className: 'minimap',
    type: 'delegate',
  });
  // 网格可用于辅助用户在拖拽节点时对齐到网格
  // 实例化 grid 插件
  const grid = new G6.Grid();



  const graph = new G6.Graph({
    container: 'mountNode',
    width: 800,
    height: 600,
    // 是否将图适配到画布大小，可以防止超出画布或留白太多
    //fitView: true, //与layout同时使用时去掉这个特性，复杂的布局系统会打破适配的规则，反而会造成更多的困扰
    // 画布上的四周留白宽度
    fitViewPadding: [20, 40, 50, 20],

    // 节点在默认状态下的样式配置（style）和其他配置
    defaultNode: {
      size: 30, // 节点大小
      // ...                 // 节点的其他配置
      // 节点样式配置
      style: {
        fill: 'steelblue', // 节点填充色
        stroke: '#666', // 节点描边色
        lineWidth: 1, // 节点描边粗细
      },
      // 节点上的标签文本配置
      labelCfg: {
        // 节点上的标签文本样式配置
        style: {
          fill: '#fff', // 节点标签文字颜色
        },
      },
    },
    // 边在默认状态下的样式配置（style）和其他配置
    defaultEdge: {
      // ...                 // 边的其他配置
      //type: 'quadratic',   // 二阶贝塞尔曲线
      // 边样式配置
      style: {
        opacity: 0.6, // 边透明度
        stroke: 'grey', // 边描边颜色
        endArrow: {
          // 使用内置箭头路径函数，参数为箭头的 宽度、长度、偏移量（默认为 0，与 d 对应）
          path: G6.Arrow.triangle(7, 7, 0),
          d: 0
        },
      },
      // 边上的标签文本配置
      labelCfg: {
        autoRotate: true, // 边上的标签文本根据边的方向旋转
      },
    },

    //图布局 Layout
    layout: {
      // Object，可选，布局的方法及其配置项，默认为 random 布局。
      // type: 'force', // 指定为力导向布局
      type: 'force2', // 指定为力导向布局
      //animate: true, // 是否每次迭代都刷新画布, force需开启避免页面有卡顿感, force2开启后抖动过于频繁
      preventOverlap: true, // 防止节点重叠
      // nodeSize: 30        // 节点大小，用于算法中防止节点重叠时的碰撞检测。由于已经在上一节的元素配置中设置了每个节点的 size 属性，则不需要在此设置 nodeSize。
      linkDistance: 50, // 指定边距离
    },

    //交互行为 Behavior
    modes: {
      // 允许拖拽画布、放缩画布、拖拽节点
      default: [ 'drag-node', 'drag-canvas', 'zoom-canvas',
        // 点提示框交互工具的配置
        {
          type: 'tooltip',
          formatText(model) {
            const text = 'label: ' + model.label
              + '<br/> class: ' + model.class;
            return text;
          },
          shouldUpdate: e => {
            return true;
          }
        },
        // 边提示框交互工具的配置
        {
          type: 'edge-tooltip',
          formatText(model) {
            const text = 'source: ' + model.source
              + '<br/> target: ' + model.target
              + '<br/> weight: ' + model.weight;
            return text;
          },
          shouldUpdate: e => {
            return true;
          }
        }
      ]
    },

    // 交互状态 State
    // 节点不同状态下的样式集合
    // 样式 + 点击事件绑定 => 点击图后样式变化
    nodeStateStyles: {
      // 鼠标 hover 上节点，即 hover 状态为 true 时的样式
      hover: {
        fill: 'lightsteelblue',
      },
      // 鼠标点击节点，即 click 状态为 true 时的样式
      click: {
        stroke: '#000',
        lineWidth: 3,
      },
    },
    // 边不同状态下的样式集合
    edgeStateStyles: {
      // 鼠标点击边，即 click 状态为 true 时的样式
      click: {
        stroke: 'steelblue',
        //stroke: '#CCE8CF',
      },
    },


    plugins: [minimap, grid], // 将 minimap, grid 实例配置到图上

  });

  const data = {
    "nodes": [
      //案例1
      {"id": "01", "label": "人01", "class": "c0"},
      {"id": "02", "label": "人02", "class": "c0"},
      {"id": "03", "label": "人03", "class": "c0"},
      {"id": "04", "label": "人04", "class": "c0"},
      {"id": "05", "label": "人05", "class": "c0"},
      {"id": "11", "label": "企11", "class": "c1"},
      {"id": "12", "label": "企12", "class": "c1"},
      {"id": "13", "label": "企13", "class": "c1"},
      {"id": "14", "label": "企14", "class": "c1"},
      //案例2
      {"id": "21", "label": "人21", "class": "c0"},
      {"id": "22", "label": "人22", "class": "c0"},
      {"id": "23", "label": "人23", "class": "c0"},
      {"id": "24", "label": "人24", "class": "c0"},
      {"id": "31", "label": "非01", "class": "c2"},
      {"id": "41", "label": "企41", "class": "c1"},
      {"id": "42", "label": "企42", "class": "c1"},
      {"id": "43", "label": "企43", "class": "c1"},
      {"id": "44", "label": "企44", "class": "c1"},
    ],
    "edges": [
      //案例1
      {"source": "01", "target": "14", "label": "0.50", "weight": 0.50 },
      {"source": "02", "target": "14", "label": "0.50", "weight": 0.50 },
      {"source": "14", "target": "13", "label": "0.50", "weight": 0.50 },
      {"source": "03", "target": "13", "label": "0.25", "weight": 0.25 },
      {"source": "12", "target": "13", "label": "0.25", "weight": 0.25 },
      {"source": "13", "target": "12", "label": "0.50", "weight": 0.50 },
      {"source": "04", "target": "12", "label": "0.50", "weight": 0.50 },
      {"source": "12", "target": "11", "label": "0.50", "weight": 0.50 },
      {"source": "05", "target": "11", "label": "0.50", "weight": 0.50 },
      //案例2
      {"source": "21", "target": "44", "label": "0.50", "weight": 0.50 },
      {"source": "22", "target": "44", "label": "0.50", "weight": 0.50 },
      {"source": "44", "target": "43", "label": "0.50", "weight": 0.50 },
      {"source": "23", "target": "43", "label": "0.25", "weight": 0.25 },
      {"source": "31", "target": "43", "label": "0.25", "weight": 0.25 },
      {"source": "43", "target": "42", "label": "0.50", "weight": 0.50 },
      {"source": "24", "target": "42", "label": "0.50", "weight": 0.50 },
      {"source": "42", "target": "41", "label": "0.50", "weight": 0.50 },
      {"source": "31", "target": "41", "label": "0.50", "weight": 0.50 },
    ]
  }

  //两节点间存在多条边, 数据集处理
  G6.Util.processParallelEdges(data.edges);

  const main = async () => {
    /*const response = await fetch(
      'https://gw.alipayobjects.com/os/basement_prod/6cae02ab-4c29-44b2-b1fd-4005688febcb.json',
    );
    const remoteData = await response.json();
    graph.data(remoteData);*/

    // 可以直接将配置写入数据文件(后端处理)；也可以在读入数据后，通过遍历的方式写入配置(前端处理)。
    // 遍历点数据
    data.nodes.forEach((node) => {
      if (!node.style) {
        node.style = {};
      }
      switch (
        node.class // 根据节点数据中的 class 属性配置图形
        ) {
        case 'c0': {
          node.type = 'rect'; // class = 'c0' 时节点图形为 circle
          break;
        }
        case 'c1': {
          node.type = 'circle'; // class = 'c1' 时节点图形为 rect
          node.size = [35, 20]; // class = 'c1' 时节点大小
          break;
        }
        case 'c2': {
          node.type = 'image'; // class = 'c2' 时节点图形为 image
          node.size = 50; // class = 'c2' 时节点大小
          node.img = "https://gw.alipayobjects.com/zos/rmsportal/XuVpGqBFxXplzvLjJBZB.svg"
          break;
        }
      }
    });


    // 遍历边数据
    data.edges.forEach((edge) => {
      if (!edge.style) {
        edge.style = {};
      }
      edge.style.lineWidth = edge.weight * 4; // 边的粗细映射边数据中的 weight 属性数值
      // opacity 文本透明度
      edge.style.opacity = 0.6;
      edge.style.stroke = 'grey';
    });


    graph.data(data);

    graph.render();


    // 点击事件绑定
    // 监听鼠标进入节点
    graph.on('node:mouseenter', (e) => {
      const nodeItem = e.item;
      // 设置目标节点的 hover 状态 为 true
      graph.setItemState(nodeItem, 'hover', true);
    });
    // 监听鼠标离开节点
    graph.on('node:mouseleave', (e) => {
      const nodeItem = e.item;
      // 设置目标节点的 hover 状态 false
      graph.setItemState(nodeItem, 'hover', false);
    });
    // 监听鼠标点击节点
    graph.on('node:click', (e) => {
      // 1. 清理之前点击过的节点状态
      // 先将所有当前有 click 状态的节点的 click 状态置为 false
      const clickNodes = graph.findAllByState('node', 'click');
      clickNodes.forEach((cn) => {
        graph.setItemState(cn, 'click', false);
      });

      // 2. 当前节点状态修改
      const nodeItem = e.item;
      // 设置目标节点的 click 状态 为 true
      graph.setItemState(nodeItem, 'click', true);
    });
    // 监听鼠标点击节点
    graph.on('edge:click', (e) => {
      // 先将所有当前有 click 状态的边的 click 状态置为 false
      const clickEdges = graph.findAllByState('edge', 'click');
      clickEdges.forEach((ce) => {
        graph.setItemState(ce, 'click', false);
      });
      const edgeItem = e.item;
      // 设置目标边的 click 状态 为 true
      graph.setItemState(edgeItem, 'click', true);
    });

  };


  main();

</script>
</body>
</html>
